/*
 * @Description:
 * @Author: liyuhao
 * @Date: 2021-09-25 15:52:09
 * @LastEditors: Please set LastEditors
 * @LastEditTime: 2024-01-11 15:48:57
 */
import React, { useEffect, useRef, useState } from 'react'

export interface VirtualScrollProps {
  list?: {
    id: React.Key
    [key: string]: any
  }[]
  render: React.FC<any>
  height?: string | number // 容器高度
  scrollLength?: number // 实际滚动的list长度 数据过多时设置此参数可提升性能 当list长度小于scrollLength时 scrollLength取list长度
  hoverStop?: boolean // hover时是否停止滚动
  manual?: boolean //手工滚动
  overflowY?: 'hidden' | 'auto' |'scroll',
  speed?: number // 每秒滚动的距离(px)
  className?: string
  style?: React.CSSProperties
  extraRenderProps?: Record<any, any>
  onShow?: React.FC<any>
}

// 越小 渲染越频繁
const defaultHideItemNum = 1

const formatList = (list: any[], startIndex: number, lastIndex: number) => {
  let _list: any = []
  if (list?.length) {
    if (lastIndex > list.length) {
      _list = [
        ...list?.slice(startIndex, lastIndex),
        ...list?.slice(0, lastIndex - list.length),
      ]
    } else {
      _list = list?.slice(startIndex, lastIndex)
    }
  }
  return _list
}

const VirtualScroll: React.FC<VirtualScrollProps> = (props) => {
  const {
    list,
    render: Render,
    height,
    scrollLength = 0,
    hoverStop,
    speed = 60,
    className,
    style,
    extraRenderProps,
    onShow,
    manual = false,
    overflowY='hidden'
  } = props
  const container = useRef<any>(null)
  const _scrollLength =
    scrollLength > (list?.length || 0) ? list?.length || 0 : scrollLength
  const [listInfo, setListInfo] = useState({
    list: formatList(list || [], 0, _scrollLength),
    lastIndex: _scrollLength,
    scrollLength: _scrollLength,
    relaIndexStart: 0,
    reversal: false,
  })
  const mark = useRef<any>(null)
  const ioObersver = useRef<any>()
  const scorllTimer = useRef<any>(null)

  const handleMouseEnter = () => {
    if (hoverStop) {
      clearInterval(scorllTimer.current)
    }
    if (manual) {
      //手动状态，鼠标悬停滚动
      scorllTimer.current = setInterval(() => {
        container.current && (container.current.scrollTop += 1)
      }, 1000 / speed)
    }
  }

  const handleMouseLeave = () => {
    if (hoverStop && !manual) {
      // 已开始滚动的情况下 鼠标移出才重启滚动
      scorllTimer.current = setInterval(() => {
        container.current && (container.current.scrollTop += 1)
      }, 1000 / speed)
    }
    if (manual) {
      clearInterval(scorllTimer.current)
    }
  }

  useEffect(() => {
    const _scrollLength =
      scrollLength > (list?.length || 0) ? list?.length || 0 : scrollLength
    setListInfo({
      list: formatList(list || [], 0, _scrollLength),
      lastIndex: _scrollLength,
      scrollLength: _scrollLength,
      relaIndexStart: 0,
      reversal: false,
    })
    Promise.resolve().then(() => {
      if (scorllTimer.current) {
        clearInterval(scorllTimer.current)
      }
      if (ioObersver.current) {
        ioObersver.current.disconnect()
      }
      if (container.current.scrollHeight > container.current.offsetHeight) {
        if (!manual) {
          // 手动开启轮播
          scorllTimer.current = setInterval(() => {
            container.current && (container.current.scrollTop += 1)
          }, 1000 / speed)
        }
        ioObersver.current = new IntersectionObserver(
          (entries) => {
            if (entries[0]?.isIntersecting) {
              setListInfo((v) => {
                const {
                  lastIndex: old_lastIndex,
                  scrollLength: old_scrollLength,
                  reversal: old_reversal,
                } = v

                let reversal = v.reversal
                let lastIndex = v.lastIndex
                let scrollLength = v.scrollLength
                let startIndex
                if (list?.length) {
                  const _count =
                    old_lastIndex +
                    defaultHideItemNum -
                    list.length -
                    old_scrollLength
                  if (
                    old_lastIndex ===
                    old_scrollLength + list.length - defaultHideItemNum
                  ) {
                    // 一组循环里的最后一次和第一次渲染出的数据是一样的 需跳过最后一次 当list.length为单数时 跳过会导致relaindex单双反转 需记录已矫正
                    reversal = list.length % 2 ? !old_reversal : old_reversal
                    lastIndex = old_scrollLength
                    scrollLength = old_scrollLength
                    startIndex = lastIndex - scrollLength
                  } else if (_count > 0) {
                    lastIndex = old_scrollLength
                    scrollLength = old_scrollLength
                    startIndex = lastIndex - scrollLength
                  } else {
                    lastIndex = old_lastIndex + defaultHideItemNum
                    scrollLength = old_scrollLength
                    startIndex = lastIndex - scrollLength
                  }
                } else {
                  lastIndex = 0
                  scrollLength = old_scrollLength
                  startIndex = lastIndex - scrollLength
                }
                onShow && onShow(list?.[startIndex].id, list?.[startIndex].desc)
                return {
                  lastIndex,
                  reversal,
                  scrollLength,
                  relaIndexStart: reversal ? startIndex + 1 : startIndex,
                  list: formatList(list || [], startIndex, lastIndex),
                }
              })
            }
          },
          {
            root: container.current,
          },
        )
        ioObersver.current.observe(mark.current)
      }
    })

    return () => {
      ioObersver.current && ioObersver.current.disconnect()
      scorllTimer.current && clearInterval(scorllTimer.current)
    }
  }, [list, scrollLength, speed])
  return (
    <div
      ref={container}
      className={className}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      style={{ height, ...style, overflowY: overflowY }}
    >
      {listInfo.list.map((v: any, index: number) => (
        <Render
          key={v.id}
          index={index}
          realIndex={listInfo.relaIndexStart + index}
          {...v}
          {...extraRenderProps}
        />
      ))}
      <div
        ref={mark}
        style={{ width: '100%', height: 1, position: 'relative' }}
      ></div>
    </div>
  )
}

export default VirtualScroll
